// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/trace_event/trace_event_synthetic_delay.h"

#include <stdint.h>

#include "base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace trace_event {
    namespace {

        const int kTargetDurationMs = 100;
        // Allow some leeway in timings to make it possible to run these tests with a
        // wall clock time source too.
        const int kShortDurationMs = 10;

    } // namespace

    class TraceEventSyntheticDelayTest : public testing::Test,
                                         public TraceEventSyntheticDelayClock {
    public:
        TraceEventSyntheticDelayTest() { }
        ~TraceEventSyntheticDelayTest() override { ResetTraceEventSyntheticDelays(); }

        // TraceEventSyntheticDelayClock implementation.
        base::TimeTicks Now() override
        {
            AdvanceTime(base::TimeDelta::FromMilliseconds(kShortDurationMs / 10));
            return now_;
        }

        TraceEventSyntheticDelay* ConfigureDelay(const char* name)
        {
            TraceEventSyntheticDelay* delay = TraceEventSyntheticDelay::Lookup(name);
            delay->SetClock(this);
            delay->SetTargetDuration(
                base::TimeDelta::FromMilliseconds(kTargetDurationMs));
            return delay;
        }

        void AdvanceTime(base::TimeDelta delta) { now_ += delta; }

        int64_t TestFunction()
        {
            base::TimeTicks start = Now();
            {
                TRACE_EVENT_SYNTHETIC_DELAY("test.Delay");
            }
            return (Now() - start).InMilliseconds();
        }

        int64_t AsyncTestFunctionBegin()
        {
            base::TimeTicks start = Now();
            {
                TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("test.AsyncDelay");
            }
            return (Now() - start).InMilliseconds();
        }

        int64_t AsyncTestFunctionEnd()
        {
            base::TimeTicks start = Now();
            {
                TRACE_EVENT_SYNTHETIC_DELAY_END("test.AsyncDelay");
            }
            return (Now() - start).InMilliseconds();
        }

    private:
        base::TimeTicks now_;

        DISALLOW_COPY_AND_ASSIGN(TraceEventSyntheticDelayTest);
    };

    TEST_F(TraceEventSyntheticDelayTest, StaticDelay)
    {
        TraceEventSyntheticDelay* delay = ConfigureDelay("test.Delay");
        delay->SetMode(TraceEventSyntheticDelay::STATIC);
        EXPECT_GE(TestFunction(), kTargetDurationMs);
    }

    TEST_F(TraceEventSyntheticDelayTest, OneShotDelay)
    {
        TraceEventSyntheticDelay* delay = ConfigureDelay("test.Delay");
        delay->SetMode(TraceEventSyntheticDelay::ONE_SHOT);
        EXPECT_GE(TestFunction(), kTargetDurationMs);
        EXPECT_LT(TestFunction(), kShortDurationMs);

        delay->SetTargetDuration(
            base::TimeDelta::FromMilliseconds(kTargetDurationMs));
        EXPECT_GE(TestFunction(), kTargetDurationMs);
    }

    TEST_F(TraceEventSyntheticDelayTest, AlternatingDelay)
    {
        TraceEventSyntheticDelay* delay = ConfigureDelay("test.Delay");
        delay->SetMode(TraceEventSyntheticDelay::ALTERNATING);
        EXPECT_GE(TestFunction(), kTargetDurationMs);
        EXPECT_LT(TestFunction(), kShortDurationMs);
        EXPECT_GE(TestFunction(), kTargetDurationMs);
        EXPECT_LT(TestFunction(), kShortDurationMs);
    }

    TEST_F(TraceEventSyntheticDelayTest, AsyncDelay)
    {
        ConfigureDelay("test.AsyncDelay");
        EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs);
        EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2);
    }

    TEST_F(TraceEventSyntheticDelayTest, AsyncDelayExceeded)
    {
        ConfigureDelay("test.AsyncDelay");
        EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs);
        AdvanceTime(base::TimeDelta::FromMilliseconds(kTargetDurationMs));
        EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs);
    }

    TEST_F(TraceEventSyntheticDelayTest, AsyncDelayNoActivation)
    {
        ConfigureDelay("test.AsyncDelay");
        EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs);
    }

    TEST_F(TraceEventSyntheticDelayTest, AsyncDelayNested)
    {
        ConfigureDelay("test.AsyncDelay");
        EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs);
        EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs);
        EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs);
        EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2);
    }

    TEST_F(TraceEventSyntheticDelayTest, AsyncDelayUnbalanced)
    {
        ConfigureDelay("test.AsyncDelay");
        EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs);
        EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2);
        EXPECT_LT(AsyncTestFunctionEnd(), kShortDurationMs);

        EXPECT_LT(AsyncTestFunctionBegin(), kShortDurationMs);
        EXPECT_GE(AsyncTestFunctionEnd(), kTargetDurationMs / 2);
    }

    TEST_F(TraceEventSyntheticDelayTest, ResetDelays)
    {
        ConfigureDelay("test.Delay");
        ResetTraceEventSyntheticDelays();
        EXPECT_LT(TestFunction(), kShortDurationMs);
    }

    TEST_F(TraceEventSyntheticDelayTest, BeginParallel)
    {
        TraceEventSyntheticDelay* delay = ConfigureDelay("test.AsyncDelay");
        base::TimeTicks end_times[2];
        base::TimeTicks start_time = Now();

        delay->BeginParallel(&end_times[0]);
        EXPECT_FALSE(end_times[0].is_null());

        delay->BeginParallel(&end_times[1]);
        EXPECT_FALSE(end_times[1].is_null());

        delay->EndParallel(end_times[0]);
        EXPECT_GE((Now() - start_time).InMilliseconds(), kTargetDurationMs);

        start_time = Now();
        delay->EndParallel(end_times[1]);
        EXPECT_LT((Now() - start_time).InMilliseconds(), kShortDurationMs);
    }

} // namespace trace_event
} // namespace base
